// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.

package contact

import (
	"context"
	"fmt"
	"sync"
	"time"

	"go.uber.org/zap"

	"storj.io/common/pb"
	"storj.io/common/rpc"
	"storj.io/common/rpc/rpcstatus"
	"storj.io/common/storj"
	"storj.io/storj/satellite/overlay"
)

// Config contains configurable values for contact service.
type Config struct {
	ExternalAddress string        `user:"true" help:"the public address of the node, useful for nodes behind NAT" default:""`
	Timeout         time.Duration `help:"timeout for pinging storage nodes" default:"10m0s" testDefault:"1m"`

	RateLimitInterval  time.Duration `help:"the amount of time that should happen between contact attempts usually" releaseDefault:"1s" devDefault:"1ns"`
	RateLimitBurst     int           `help:"the maximum burst size for the contact rate limit token bucket" releaseDefault:"2" devDefault:"1000"`
	RateLimitCacheSize int           `help:"the number of nodes or addresses to keep token buckets for" default:"1000"`
}

// Service is the contact service between storage nodes and satellites.
// It is responsible for updating general node information like address and capacity.
// It is also responsible for updating peer identity information for verifying signatures from that node.
//
// architecture: Service
type Service struct {
	log *zap.Logger

	mutex sync.Mutex
	self  *overlay.NodeDossier

	overlay *overlay.Service
	peerIDs overlay.PeerIdentities
	dialer  rpc.Dialer

	timeout   time.Duration
	idLimiter *RateLimiter
	//udpConn *net.UDPConn
}

// NewService creates a new contact service.
func NewService(log *zap.Logger, self *overlay.NodeDossier, overlay *overlay.Service, peerIDs overlay.PeerIdentities, dialer rpc.Dialer, config Config) *Service {
	return &Service{
		log:       log,
		self:      self,
		overlay:   overlay,
		peerIDs:   peerIDs,
		dialer:    dialer,
		timeout:   config.Timeout,
		idLimiter: NewRateLimiter(config.RateLimitInterval, config.RateLimitBurst, config.RateLimitCacheSize),
	}
}

// Local returns the satellite node dossier.
func (service *Service) Local() overlay.NodeDossier {
	service.mutex.Lock()
	defer service.mutex.Unlock()
	return *service.self
}
//func  (service *Service)SetUdpConn(c *net.UDPConn)  {
//	service.udpConn = c
//}

// Close closes resources.
func (service *Service) Close() error { return nil }

// PingBack pings the node to test connectivity.
func (service *Service) PingBack(ctx context.Context, nodeurl storj.NodeURL) (_ bool, _ bool, _ string, err error) {
	defer mon.Task()(&ctx)(&err)

	if service.timeout > 0 {
		var cancel func()
		ctx, cancel = context.WithTimeout(ctx, service.timeout)
		defer cancel()
	}

	pingNodeSuccess := true
	var pingErrorMessage string
	var pingNodeSuccessQUIC bool

	pingNodeSuccessQUIC = true
	err = service.pingNodeQUIC(ctx, nodeurl)
	if err != nil {
		// udp ping back is optional right now, it shouldn't affect contact service's
		// control flow
		service.log.Error("pingBack pingNode error",zap.String("P2PAddress", nodeurl.P2PAddress),
			zap.String("ip",nodeurl.Address),zap.String("pingErrorMessage", pingErrorMessage))
		pingNodeSuccessQUIC = false
		pingErrorMessage = err.Error()
	}
	//service.log.Info("pingBack pingNode  end")

	return pingNodeSuccess, pingNodeSuccessQUIC, pingErrorMessage, nil
}

func (service *Service) pingNodeQUIC(ctx context.Context, nodeurl storj.NodeURL) error {
	udpDialer := service.dialer
	//udpDialer.Connector = quic.NewDefaultConnector(nil)
	//ljg
	//if service.udpConn != nil{
	//	udpDialer.Connector = quic.NewDefaultConnectorWithOpt(nil,true, service.udpConn)
	//}else{
	//	udpDialer.Connector = quic.NewDefaultConnector(nil)
	//}

	udpClient, err := dialNodeURL(ctx, udpDialer, nodeurl)
	if err != nil {
		mon.Event("failed_dial_quic")
		return Error.New("failed to dial storage node (ID: %s) at address %s using QUIC: %q", nodeurl.ID.String(), nodeurl.Address, err)
	}
	defer func() {
		_ = udpClient.Close()
	}()

	_, err = udpClient.pingNode(ctx, &pb.ContactPingRequest{})
	if err != nil {
		mon.Event("failed_ping_node_quic")
		return Error.New("failed to ping storage node using QUIC, your node indicated error code: %d, %q", rpcstatus.Code(err), err)
	}

	return nil
}
func (service *Service) NotifyGateway(ctx context.Context,nodeurl storj.NodeURL ,gateway string)error{

	//if service.timeout > 0 {
	//	var cancel func()
	//	ctx, cancel = context.WithTimeout(ctx, service.timeout)
	//	defer cancel()
	//}

	notifyTimeout := time.Second*6
	var cancel func()
	ctx, cancel = context.WithTimeout(ctx, notifyTimeout)
	defer cancel()

	udpDialer := service.dialer
	//if service.udpConn == nil{
	//	service.log.Error("service.udpConn is nil")
	//	return  Error.New("udpConn == nil")
	//}
	//udpDialer.Connector = quic.NewDefaultConnectorWithOpt(nil,true, service.udpConn)


	udpClient, err := dialNodeURL(ctx, udpDialer, nodeurl)
	if err != nil {
		service.log.Error("failed_dial_quic", zap.String("ip" ,gateway))
		fmt.Println(err)
		mon.Event("failed_dial_quic")
		return Error.New("failed to dial storage node (ID: %s) at address %s using QUIC: %q", nodeurl.ID.String(), nodeurl.Address, err)
	}
	defer func() {
		_ = udpClient.Close()
	}()

	_, err = udpClient.NotifyGateway(ctx,&pb.GatewayRequest{
		Address:              gateway,
		Count:                3,
	})


	if err != nil {
		mon.Event("failed_ping_node_quic")
		return Error.New("failed to ping storage node using QUIC, your node indicated error code: %d, %q", rpcstatus.Code(err), err)
	}

	return nil
}